package com.example.springboot.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.example.springboot.entities.User;
import com.example.springboot.entities.User2;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @version 1.0
 * @author:TRS
 * @create:2023/2/23 9:49
 * @description:
 * @ClassName: RedisConfig
 * @company TRS
 */
@Configuration
@EnableAutoConfiguration
public class RedisConfig {

    private static final long DEFAULT_TIME_TO_LIVE = 10 * 60L; //默认存活时间 10分钟  //7 * 24 * 3600L;// 缓存的默认存活时间是7天
    private static final String DEFAULT_CACHE_SEPARATOR = ":"; // 缓存key的默认分隔符

    public RedisSerializer<Object> fastJson2JsonRedisSerializer() {
        return new FastJsonRedisSerializer<>(Object.class);
    }

    @Resource
    private ApplicationContext applicationContext;

    private Map<String, RedisCacheConfiguration> buildInitCaches() {
        HashMap<String, RedisCacheConfiguration> cacheConfigMap = new HashMap<>();
        String[] beanNamesForAnnotation = applicationContext.getBeanNamesForAnnotation(CacheConfig.class);
        Object userService = applicationContext.getBean("userService");
        List<? extends Class<?>> collects = Arrays.stream(beanNamesForAnnotation).map(applicationContext::getType).filter(Objects::nonNull).collect(Collectors.toList());
        for (Class<?> collect : collects) {
            List<Method> noCacheNamesMethod = new ArrayList<>();
            ReflectionUtils.doWithMethods(collect, method -> {
                ReflectionUtils.makeAccessible(method);
                Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
                if (Objects.nonNull(cacheable)) {
                    String[] cacheNames = cacheable.cacheNames();  // cacheNames如果没写，会出现空数组
                    if (cacheNames.length == 0) {
                        noCacheNamesMethod.add(method);
                        return;
                    }
                    for (String cacheName : cacheNames) {
                        cacheConfigMap.put(cacheName, getRedisCacheConfigurationByType(method.getGenericReturnType()));
                    }
                }
            });
            if (noCacheNamesMethod.size() > 0){
                CacheConfig annotation = AnnotationUtils.findAnnotation(collect, CacheConfig.class);
                String[] cacheNames = annotation.cacheNames();
                if (cacheNames.length == 0) {
                    String name = collect.getName();
                    throw new RuntimeException(name + "类或其中的方法至少有一个cacheNames");
                }
                for (Method method : noCacheNamesMethod) {
                    for (String cacheName : cacheNames) {
                        cacheConfigMap.put(cacheName, getRedisCacheConfigurationByType(method.getGenericReturnType()));
                    }
                }
            }
        }
        return cacheConfigMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationByType(Type type) {
        RedisSerializer<String> keySerializer = new StringRedisSerializer();
        MyFastJsonRedisSerializer<Object> valueSerializer = new MyFastJsonRedisSerializer<>(type);

        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        return config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer));
    }

    // 自定义序列化方式
    public class MyFastJsonRedisSerializer<T> implements RedisSerializer<T> {

        private final Type type;

        public MyFastJsonRedisSerializer(Type type) {
            this.type = type;
        }

        @Override
        public byte[] serialize(T t) throws SerializationException {
            return JSON.toJSONBytes(t);
        }

        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            return JSON.parseObject(bytes,type);
        }
    }

    /**
     * 解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题，https://blog.csdn.net/Julyraining/article/details/108408639
     * Jackson2JsonRedisSerializer：1、不能反序列化某个对象，2、反序列化list时，List中的类型为ListedHashMap
     * GenericJackson2JsonRedisSerializer：序列化的数据中存在类路径，类路径变了无法反序列化
     * JdkSerializationRedisSerializer：序列化出来为乱码，无法查看
     * @param factory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // LocalDateTime的序列化和反序列化配置:https://www.codenong.com/cs106758473/
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 支持 jdk 1.8 日期
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(new Jdk8Module())
                .registerModule(new JavaTimeModule())
                .registerModule(new ParameterNamesModule());
        jackson2JsonRedisSerializer.setObjectMapper(om);


        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .computePrefixWith(name -> name + ":")
                .entryTtl(Duration.ofSeconds(DEFAULT_TIME_TO_LIVE));


        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(redisCacheConfiguration)
//                .withInitialCacheConfigurations(buildInitCaches())
                .build();
        return cacheManager;
    }

    /**
     * 解决cache(@Cacheable)把数据缓存到redis中的value是乱码问题，https://blog.csdn.net/Julyraining/article/details/108408639
     * Jackson2JsonRedisSerializer：1、不能反序列化某个对象，2、反序列化list时，List中的类型为ListedHashMap
     * GenericJackson2JsonRedisSerializer：序列化的数据中存在类路径，类路径变了无法反序列化
     * JdkSerializationRedisSerializer：序列化出来为乱码，无法查看
     * @param factory
     * @return
     */
//    @Bean
//    public CacheManager cacheManager(RedisConnectionFactory factory) {
//        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//
//        // 配置序列化
//        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
//                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJson2JsonRedisSerializer()));
//
//
//        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
//                .cacheDefaults(redisCacheConfiguration)
//                .withInitialCacheConfigurations(buildInitCaches())
//                .build();
//        return cacheManager;
//    }

}